/*   
 * Copyright 2010 Rob Schoening
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 *    
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.fortasoft.hatfield.vaadin.toolkit;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.fortasoft.hatfield.util.exception.BeaconException;
import org.fortasoft.hatfield.util.expression.ExpressionEngine;
import org.fortasoft.hatfield.util.expression.ExpressionEngineFactory;
import org.beaconframework.vaadin.data.provider.AbstractItemContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vaadin.data.Item;
import com.vaadin.data.Property;

public class ExpressionItemContainer<T> extends AbstractItemContainer<T> {

    private static final long serialVersionUID = 6343634952141118890L;
    private static Logger log = LoggerFactory
            .getLogger(ExpressionItemContainer.class);

    private String expressionEngineName = "spring";

    public class ExpressionItem implements Item {

        /**
         * 
         */
        private static final long serialVersionUID = 4932766653833494970L;
        private Object item;


        public ExpressionItem(Object item) {
            this.item = item;
        }


        @Override
        public boolean addItemProperty(Object id, Property property)
                throws UnsupportedOperationException {
            throw new UnsupportedOperationException();
        }


        @Override
        public Property getItemProperty(Object id) {

            PropertyDefinition def = ExpressionItemContainer.this
                    .getPropertyDefinition(id);
            if (def == null) {
                return AbstractItemContainer.EMPTY_PROPERTY;
            }
            return new ExpressionItemProperty(item, def);

        }


        @Override
        public Collection<?> getItemPropertyIds() {
            return Collections
                    .unmodifiableCollection(getContainerPropertyIds());
        }


        @Override
        public boolean removeItemProperty(Object id)
                throws UnsupportedOperationException {
            return false;
        }


        public void addItemProperty(Object id, String expression,
                Class<? extends Object> c) {
            throw new UnsupportedOperationException();
        }
    }


    public ExpressionItemContainer() {
        super();
    }


    @Override
    protected Item newItem(T pojo) {
        ExpressionItem bi = new ExpressionItem(pojo);
        return bi;
    }


    @Override
    public boolean addContainerProperty(Object propertyId, Class<?> type,
            Object defaultValue) throws UnsupportedOperationException {

        return addContainerProperty(propertyId, type, defaultValue, propertyId
                .toString());
    }


    public boolean addContainerProperty(Object propertyId, Class<?> type,
            Object defaultValue, String expressionString)
            throws UnsupportedOperationException {

        boolean b = super.addContainerProperty(propertyId, type, defaultValue);

        getPropertyDefinition(propertyId).setContainerConfig(expressionString);

        return b;
    }

    public class ExpressionItemProperty implements Property {

        /**
         * 
         */
        private static final long serialVersionUID = 3556568997042738768L;
        Object object;
        PropertyDefinition propertyDef;


        public ExpressionItemProperty(Object object, PropertyDefinition def) {
            super();
            this.object = object;
            this.propertyDef = def;
        }


        protected String getExpressionString() {
            return (String) propertyDef.getContainerConfig();
        }


        @Override
        public Class<?> getType() {
            return propertyDef.getPropertyType();
        }


        @Override
        public Object getValue() {
            String expression = getExpressionString();

            if (expression == null) {
                return propertyDef.getDefaultValue();
            }
            try {
                Map<String, Object> m = new HashMap<String, Object>();
                m.put("root", object);
                addContextVariables(m);
                return getExpressionEngine().evaluate(expression, m);
            } catch (RuntimeException e) {
                throw e;
            } catch (Exception e) {

                throw new BeaconException(e);

            }

        }


        @Override
        public boolean isReadOnly() {

            return true;
        }


        @Override
        public void setReadOnly(boolean newStatus) {
            if (newStatus == false) {
                throw new UnsupportedOperationException();
            }
        }


        @Override
        public void setValue(Object newValue) throws ReadOnlyException,
                ConversionException {
            throw new UnsupportedOperationException();

        }


        public String toString() {

            Object obj = getValue();
            if (obj == null) {
                return null;
            } else {
                return obj.toString();
            }
        }

    }


    public ExpressionEngine getExpressionEngine() {
        return ExpressionEngineFactory
                .createExpressionEngine(getExpressionEngineName());
    }


    void addContextVariables(Map<String, Object> vars) {
        // nothing to add
    }


    public String getExpressionEngineName() {
        return expressionEngineName;
    }


    public void setExpressionEngineName(String expressionEngineName) {
        log.debug("using expression engine: {}", expressionEngineName);
        this.expressionEngineName = expressionEngineName;
    }

}
